{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "7491bf6c-a56a-4118-a362-c0a0f62d2dc7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T09:30:32.482252Z",
     "iopub.status.busy": "2025-07-23T09:30:32.481718Z",
     "iopub.status.idle": "2025-07-23T09:30:36.158191Z",
     "shell.execute_reply": "2025-07-23T09:30:36.157170Z",
     "shell.execute_reply.started": "2025-07-23T09:30:32.482252Z"
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "from openai import OpenAI\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from typing import Dict, List, Optional, Tuple, Union\n",
    "\n",
    "import PyPDF2\n",
    "import markdown\n",
    "import html2text\n",
    "import json\n",
    "from tqdm import tqdm\n",
    "import tiktoken\n",
    "import re\n",
    "from bs4 import BeautifulSoup\n",
    "from IPython.display import display, Code, Markdown"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "0ae07fb1-90b5-4e9a-a7f1-aa154c94bdf0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:23:18.245636Z",
     "iopub.status.busy": "2025-07-23T05:23:18.245636Z",
     "iopub.status.idle": "2025-07-23T05:23:19.188823Z",
     "shell.execute_reply": "2025-07-23T05:23:19.187761Z",
     "shell.execute_reply.started": "2025-07-23T05:23:18.245636Z"
    }
   },
   "outputs": [],
   "source": [
    "api_key=\"hk-xxx\"\n",
    "base_url=\"https://api.openai-hk.com/v1\"\n",
    "# 实例化客户端\n",
    "client = OpenAI(api_key=api_key,base_url=base_url)\n",
    "# 临时设置环境变量\n",
    "os.environ[\"OPENAI_API_KEY\"] = api_key\n",
    "os.environ[\"OPENAI_BASE_URL\"] = base_url\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9dc75113-a76f-4f4e-89ab-8ed63261c0c6",
   "metadata": {},
   "source": [
    "# 文本向量化模块创建"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "acfb3bd0-8f34-4437-8c29-0f349334694e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:23:33.273541Z",
     "iopub.status.busy": "2025-07-23T05:23:33.272507Z",
     "iopub.status.idle": "2025-07-23T05:23:35.791017Z",
     "shell.execute_reply": "2025-07-23T05:23:35.790036Z",
     "shell.execute_reply.started": "2025-07-23T05:23:33.273541Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.0060808719135820866, 0.01937379501760006, 0.01978626847267151, -0.04902195185422897, 0.005962129216641188, -0.05264672636985779, -0.026648342609405518, -0.012949194759130478, 0.0021545535419136286, -0.02132367342710495, 0.017861388623714447, 0.0028310739435255527, -0.01738641783595085, 0.016573969274759293, 0.02018624357879162, 0.061296187341213226, -0.007674522697925568, -0.009818139486014843, -0.021473664790391922, 0.030473103746771812, 0.024323487654328346, 0.012936695478856564, 0.010143118910491467, -0.034522853791713715, -0.012192991562187672, -0.0307230893522501, -0.03832261636853218, -0.03187301754951477, 0.0637960284948349, -0.05794639512896538, -0.03424786776304245, -0.04184739664196968, 0.018648840487003326, -0.016311485320329666, -0.004121618811041117, 0.039397548884153366, 0.04609713330864906, -0.03932255506515503, 0.010399352759122849, -0.08414476364850998, -0.022611092776060104, 0.006393352057784796, 0.023748522624373436, 0.014511597342789173, 0.023311050608754158, 0.03654772788286209, -0.025073440745472908, 0.012574218213558197, 0.023011067882180214, 0.02077370695769787, -0.025335924699902534, 0.006855823565274477, -0.014311609789729118, -0.03029811568558216, 0.018136370927095413, 0.01741141639649868, -0.03012312576174736, 0.04982190206646919, 0.012018002569675446, -0.023336049169301987, 0.040197499096393585, -0.01036185584962368, -0.02267359010875225, 0.02759828232228756, -0.01064933743327856, -0.004534092731773853, -0.03252297639846802, -0.016461476683616638, -0.009886885061860085, -0.01954878307878971, 0.05594652146100998, 0.015124059282243252, -0.06009626016020775, -0.010574341751635075, -0.007505782879889011, -0.02442348003387451, -0.04397226497530937, -0.005387164652347565, -0.02052372321486473, -0.02799825742840767, 0.011855512857437134, 0.026648342609405518, -0.004912194330245256, -0.017861388623714447, -0.008411976508796215, -0.010811827145516872, -0.051946770399808884, 0.024535974487662315, 0.010661836713552475, -0.039397548884153366, -0.007705770432949066, -0.03249797970056534, 0.006718331947922707, -0.013199178501963615, 0.05184677615761757, 0.001568652456626296, -0.036097753793001175, -0.017661402001976967, -0.025573408231139183, -0.004249735735356808, 0.05749642476439476, 0.008049499243497849, -0.006152742076665163, 0.04442223533987999, -0.03737267479300499, 0.011186803691089153, -0.02187363989651203, -0.049196939915418625, -0.06469597667455673, 0.013649150729179382, -0.05679646506905556, 0.006205863784998655, -0.03154803812503815, 0.004337230231612921, 0.022761084139347076, 0.016361482441425323, 0.0414724200963974, -0.0644959881901741, 0.013911634683609009, -0.04289733245968819, 0.01036810502409935, 0.019473789259791374, -0.0029138813260942698, -0.029598159715533257, -0.017323922365903854, -0.0332479327917099, 0.028098251670598984, -0.02386101521551609, -0.03192301467061043, 0.0096931466832757, -0.005846511106938124, -0.009818139486014843, 0.008349481038749218, -0.006437099538743496, -0.0630960762500763, 0.014274111948907375, -0.02231111191213131, 0.0634460523724556, -0.040397487580776215, 0.043522290885448456, 0.06234612315893173, -0.03202300891280174, 0.006424600258469582, 0.03322293236851692, -0.03604775667190552, -0.030748087912797928, -0.03164803236722946, -0.030998071655631065, 0.0010593091137707233, 0.03009812720119953, -0.006843324285000563, -0.0077932653948664665, -0.05304669961333275, 0.026298364624381065, 0.013911634683609009, 8.236792200477794e-05, 0.020673714578151703, -0.03232298791408539, -0.05489658564329147, 0.009886885061860085, 0.02368602715432644, -0.018036378547549248, 0.001867071376182139, 0.0072557986713945866, 0.0024951572995632887, -0.016586467623710632, 0.039422549307346344, 0.01437410619109869, -0.04807201027870178, -0.005805888678878546, -0.017173931002616882, -0.06139618158340454, -0.004777827765792608, 0.011480535380542278, -0.06704583019018173, 0.010205615311861038, -0.026948323473334312, -0.0032216745894402266, -0.008905695751309395, 0.02387351542711258, 0.02774827368557453, 0.06314606964588165, -0.00019227318989578635, 0.03602275997400284, -0.012230489403009415, -0.06359604746103287, 0.033747900277376175, 0.053696658462285995, 0.005277796648442745, 0.0049590663984417915, -0.011518033221364021, -0.026298364624381065, 0.03134804964065552, -0.008649461902678013, -4.828801320400089e-05, -0.0742453783750534, 0.06824575364589691, -0.023748522624373436, -0.020086251199245453, 0.04924693703651428, 0.007468285504728556, 0.03672271594405174, -0.03954754024744034, 0.02682333067059517, -0.028498226776719093, -0.017673900350928307, -0.024723462760448456, -0.014574093744158745, -0.010924320667982101, -0.06254611164331436, 0.09554405510425568, 0.016973944380879402, 0.021473664790391922, -0.004715331830084324, -0.0085182199254632, 0.002124867867678404, 0.0036872706841677427, 0.02304856665432453, 0.015261550433933735, 0.005118431523442268, -0.008249486796557903, 0.039422549307346344, 0.052696723490953445, 0.03212300315499306, 0.0038716341368854046, 0.057796403765678406, -0.0002628742658998817, -0.03147304058074951, 0.0037903892807662487, -0.07599527388811111, 0.034347862005233765, -0.0038091379683464766, -0.007074560038745403, -0.036072757095098495, 0.01761140488088131, 0.01606150157749653, -0.004562216345220804, 0.00040114691364578903, -0.04319731146097183, -0.006137118209153414, -0.0735454261302948, 0.0018717586062848568, 0.021811142563819885, 0.017161432653665543, 0.022823579609394073, -0.011618027463555336, -0.02602338045835495, -0.04424724727869034, 0.03194801136851311, 0.02114868350327015, 0.04054747894406319, 0.007724519353359938, -0.01191800832748413, -0.019711274653673172, 0.0024670339189469814, 0.014024127274751663, 0.005587152671068907, 0.01799887977540493, 0.012242988683283329, 0.00703706219792366, -0.032173000276088715, 0.011243050917983055, -0.03342292085289955, -0.007149555254727602, -0.015224052593111992, 0.030798085033893585, 0.05029687285423279, 0.02031123638153076, 0.0461721271276474, -0.06729581207036972, -0.007993252947926521, 0.017886387184262276, 0.046047136187553406, 0.005405913572758436, -0.03662272170186043, 0.03809763118624687, -0.03604775667190552, -0.02562340535223484, 0.01437410619109869, 0.0355977863073349, -0.040797460824251175, 0.00635585468262434, -0.01759890466928482, -0.03494782745838165, -0.03312293812632561, -0.013749144971370697, -0.010111871175467968, 0.00888694729655981, 0.039022572338581085, 0.00729329651221633, 0.006027750205248594, -0.002368602668866515, 0.029998134821653366, -0.022836079820990562, 0.00938691571354866, 0.028223244473338127, 0.02114868350327015, -0.033947888761758804, 0.010218114592134953, -0.018911324441432953, 0.0014514721697196364, 0.004377852659672499, 0.05619650334119797, 0.016586467623710632, -0.006352729629725218, 0.015736520290374756, 0.004802826326340437, -0.038247622549533844, -0.01524905115365982, -0.003646648256108165, 0.0036185248754918575, -0.004434098955243826, -0.01644897647202015, -0.01897381991147995, -0.035522788763046265, -0.06369603425264359, 0.026098376139998436, -0.004955941811203957, 0.011980504728853703, -0.04044748470187187, 0.02151116169989109, -0.04052247852087021, 0.032947950065135956, 0.02737329714000225, -0.030823081731796265, 0.026898326352238655, -0.005396539345383644, 0.007643274497240782, 0.03909756615757942, -0.0031591784209012985, -0.03437286242842674, -0.05082183703780174, 0.01397413108497858, -0.015311547555029392, 0.05149679630994797, -0.004880946595221758, -0.019423792138695717, 0.11459287256002426, -0.0008351043215952814, 0.00801825150847435, -0.061746157705783844, 0.0010858699679374695, 0.014811578206717968, 0.016098998486995697, 0.019223803654313087, -0.019061313942074776, 0.0109555684030056, -0.014511597342789173, 0.027873266488313675, 0.05254673212766647, 0.020298738032579422, 0.0654459297657013, 0.02149866335093975, 0.026648342609405518, 0.008668210357427597, -0.0570964477956295, -0.00344666070304811, 0.021773645654320717, 0.03937255218625069, -0.017661402001976967, -0.00410599447786808, 0.004943442530930042, 0.02697332203388214, 0.001544435159303248, 0.009561905637383461, 0.015586530789732933, -0.036472730338573456, -0.004065372049808502, 0.02444847859442234, -0.008030750788748264, -0.0012600778136402369, 0.060546234250068665, -0.00012606637028511614, 0.010468099266290665, -0.06839574873447418, -0.016123997047543526, -0.01817386969923973, 0.03799763694405556, -0.006824575364589691, 0.032947950065135956, -0.016861451789736748, 0.023136060684919357, -0.014461600221693516, -0.002801388269290328, 0.02269858866930008, -0.0022670463658869267, -0.011168055236339569, 0.04329730570316315, 0.008724457584321499, -6.1916316553833894e-06, 0.027523288503289223, 0.046447109431028366, 0.015599030070006847, 0.00950565841048956, 0.028223244473338127, 0.0009202552610076964, 0.023348547518253326, 0.05479659140110016, 0.0042591099627316, -0.004112244118005037, 0.03147304058074951, -0.0015452164225280285, -0.05319669097661972, 0.026273366063833237, -0.019473789259791374, 0.005071559455245733, 0.0036247745156288147, 0.011105558834969997, -0.0025857766158878803, -0.018061377108097076, 0.025523412972688675, -0.027848267927765846, 0.015024065040051937, -0.016136495396494865, 0.004140367265790701, -0.03599775955080986, 0.013324171304702759, 0.05384664982557297, -0.019223803654313087, -0.0209111999720335, -0.0324229821562767, -0.030473103746771812, -0.038047634065151215, 0.04439723864197731, -0.008461973629891872, 0.01819886825978756, 0.042372364550828934, -0.01916130818426609, 0.044172253459692, -0.03874759003520012, -0.0270233191549778, 0.007018313277512789, -0.008543218486011028, -0.0077182697132229805, -0.04357228800654411, -0.0111618060618639, -0.012192991562187672, 0.049596913158893585, 0.006143367849290371, -0.011055562645196915, 0.010849324986338615, -0.014024127274751663, -0.04539717733860016, 0.0010530594736337662, -0.018523847684264183, 0.0045403423719108105, 0.029698152095079422, 0.03592276573181152, -0.019636278972029686, -0.04212237894535065, -0.001060090260580182, -0.023311050608754158, 0.025698401033878326, -0.030973073095083237, 0.00475282920524478, -0.002692020032554865, -0.0012186742387712002, -0.013561656698584557, 0.03622274845838547, -0.05264672636985779, 0.018661338835954666, 0.007380791008472443, -0.009424413554370403, 0.01607399992644787, -0.0039528789930045605, -0.03622274845838547, 0.059396304190158844, -0.0029420044738799334, 0.04087245836853981, -0.038422610610723495, -0.027048317715525627, 0.0009069748339243233, -0.03344791755080223, -0.01896131969988346, -0.018498849123716354, -0.02639835700392723, -0.004934067837893963, -0.03987251967191696, -0.029573161154985428, -0.029423169791698456, 0.03479783609509468, -0.02642335556447506, -0.012480474077165127, 0.003026374150067568, -0.03069809079170227, 0.1966877579689026, -0.016986442729830742, 0.015474037267267704, 0.004093495197594166, -0.028048254549503326, -0.004196614027023315, 0.005646523553878069, 0.01064308825880289, -0.0135116595774889, 0.01193675771355629, -0.00928692240267992, 0.033722903579473495, -0.0587463453412056, -0.022623592987656593, -0.03384789451956749, -0.0167989544570446, -0.013449163176119328, -0.04807201027870178, 0.03684770688414574, 0.05124681070446968, -0.014049125835299492, -0.035147812217473984, -0.021036190912127495, 0.03577277436852455, 0.0036122752353549004, 0.01839885488152504, -0.0040684971027076244, -0.013099185191094875, 0.02309856377542019, -0.013086685910820961, 0.036072757095098495, -0.015199054963886738, 0.0446472242474556, -0.040172502398490906, 0.010330607183277607, 0.0020233115646988153, 0.03444785624742508, -0.019686276093125343, -0.047247059643268585, -0.009324420243501663, -0.02054872177541256, -0.0013210115721449256, -0.08264485746622086, -0.002740454627200961, -0.011155555956065655, 0.006705832667648792, -0.02481095679104328, 0.04154741391539574, -0.0033497915137559175, -0.01583651453256607, -0.05829637497663498, 0.0045403423719108105, 0.018348857760429382, 0.03287295624613762, -0.012824201956391335, 0.013911634683609009, 0.0167989544570446, 0.015011565759778023, -0.04482221230864525, -0.03109806589782238, 0.018623841926455498, 0.0073370435275137424, 0.0017670775996521115, 0.0027967009227722883, -0.010393103584647179, 0.018686337396502495, -0.040422484278678894, 0.04199738800525665, 0.0065433429554104805, -0.034122876822948456, 0.006662085652351379, 0.008430725894868374, 0.0005382477538660169, 0.023936010897159576, -0.023373546078801155, 0.033922888338565826, -0.02134867198765278, -0.020861202850937843, -0.002593588549643755, -0.04172240570187569, -0.02446097880601883, 7.274938252521679e-05, -0.012767955660820007, 0.028448229655623436, -0.016323983669281006, -0.033347927033901215, 0.049596913158893585, -0.02794826216995716, 0.029773147776722908, -0.038247622549533844, -0.025148436427116394, 0.0391225665807724, -0.03494782745838165, -0.025873390957713127, 0.010918070562183857, 0.0066683352924883366, -0.016986442729830742, -0.028298240154981613, 0.024748461320996284, -0.010111871175467968, 0.0005577778210863471, 0.06564591825008392, -0.02383601665496826, -0.033547911792993546, 0.013436663895845413, 0.03597276285290718, 0.008430725894868374, 0.01046184916049242, -0.014699085615575314, 0.008474472910165787, -0.02423599176108837, 0.024111000820994377, -0.011811764910817146, -0.028048254549503326, -0.01861134171485901, 0.011099309660494328, 0.004671584349125624, 0.01407412439584732, 0.01077432930469513, 0.04317231476306915, 0.033922888338565826, 0.011249300092458725, 0.006574591156095266, 0.010393103584647179, -0.028498226776719093, -0.0035372800193727016, 0.002404537983238697, -0.012511721812188625, 0.00026326486840844154, 0.024210993200540543, 0.002110806293785572, -0.022611092776060104, -0.0007198770763352513, 0.019223803654313087, -0.001517093158327043, 0.0016998942010104656, 0.02501094341278076, 0.04517218843102455, 0.004140367265790701, -0.038072630763053894, 0.03752266615629196, -0.003934130072593689, -0.02562340535223484, -0.035097815096378326, 0.02051122486591339, -0.02814824879169464, 0.024111000820994377, 0.0006593340076506138, -0.03384789451956749, -0.007618275936692953, -0.01758640632033348, 0.056846462190151215, 0.0047434549778699875, -0.017723897472023964, -0.0030654342845082283, 0.012636713683605194, 0.006868322845548391, 0.003527905559167266, -0.033522915095090866, 0.02799825742840767, -0.018523847684264183, 0.01524905115365982, 0.0013616340002045035, -0.008643212728202343, -0.026548348367214203, -0.008786953054368496, -0.007412038743495941, 0.0013647588202729821, -0.0026342112105339766, 0.029573161154985428, -0.043497294187545776, -0.044122256338596344, -0.018148871138691902, -0.011999253183603287, 0.0006179303163662553, 0.02503594197332859, 0.030773086473345757, -0.01125555019825697, 0.005343417637050152, -0.009174428880214691, 0.030273117125034332, -0.015936508774757385, 0.003368540434166789, -0.018061377108097076, 0.03187301754951477, -0.020448727533221245, 0.020798705518245697, 0.015186555683612823, -0.004802826326340437, 0.031198060140013695, 0.04434724152088165, 0.005849636159837246, -0.03919756039977074, 0.02368602715432644, 0.023986008018255234, -0.002285795286297798, 0.016673963516950607, -0.014261612668633461, -0.02917318604886532, 0.03214799985289574, -0.010874323546886444, 0.01876133307814598, 0.003946629352867603, 0.011518033221364021, -0.00901193916797638, -0.007743268273770809, -0.02542341873049736, -0.009174428880214691, -0.005377790424972773, 0.006099620368331671, -0.010711833834648132, -0.005762141663581133, 0.0015788080636411905, -0.0057902648113667965, 0.06169616058468819, 0.012849200516939163, -0.012399228289723396, -0.010605590417981148, -0.0017358295153826475, -0.003477908670902252, -0.029823144897818565, 0.009636900387704372, -0.0015772456536069512, 0.03577277436852455, -0.010793078690767288, -0.007287046872079372, 0.0010499346535652876, -0.03309794142842293, -0.03984752297401428, -0.016086500138044357, -0.0006206645048223436, -0.004255985375493765, 0.006762079428881407, -0.006371478550136089, 0.0016905198572203517, 0.02207362651824951, 0.042947329580783844, 0.027848267927765846, -0.03467284142971039, -0.0045403423719108105, -0.03344791755080223, -0.0072183008305728436, -0.01536154467612505, 0.004630961921066046, 0.028798207640647888, 0.006518344394862652, 0.007712020073086023, 0.006974566262215376, 0.011374292895197868, 0.009524407796561718, -0.013899135403335094, -0.04612213000655174, 0.012255487963557243, 0.0005280921468511224, 0.0014381917426362634, -0.026173371821641922, -0.017323922365903854, 0.019036315381526947, 0.001362415263429284, -0.027648279443383217, 0.016373980790376663, -0.006293358281254768, 0.03817262500524521, -0.03914756327867508, 0.004315356723964214, -0.03089807741343975, 0.009299421682953835, -0.009211926721036434, -0.021961133927106857, 0.02231111191213131, -0.052946705371141434, 0.0011999253183603287, 0.01240547839552164, 0.023786019533872604, 0.053496673703193665, -0.008174491114914417, 0.028873203322291374, -0.0012608590768650174, 0.012342981994152069, -0.02133617363870144, -0.00450596958398819, 0.0008772891596890986, 0.007243299391120672, -0.0020717461593449116, 0.010849324986338615, 0.01348666101694107, -0.02917318604886532, 0.02407350204885006, 0.01243672613054514, 0.028073253110051155, -0.02344854176044464, -0.034897830337285995, 0.018498849123716354, 0.005480909254401922, 0.009618151932954788, -0.027673278003931046, 0.025535911321640015, -0.04219737648963928, -0.005680896807461977, -0.017898887395858765, -0.023898513987660408, 0.010286860167980194, 0.041347429156303406, -0.015886511653661728, -0.02582339383661747, -0.022773582488298416, -0.026073377579450607, 0.025098439306020737, 0.014811578206717968, 0.004934067837893963, -0.002599838189780712, -0.061296187341213226, -0.06269609928131104, -0.007537031080573797, -0.01842385344207287, -0.01134929433465004, -0.04717206582427025, -0.016561469063162804, 0.02717331051826477, 0.050971828401088715, -0.026073377579450607, 0.009868135675787926, 0.04339729994535446, 0.0010030625853687525, -0.012211740016937256, -0.020623717457056046, 0.003390414174646139, 0.00034255682840012014, 0.012649212963879108, -0.03187301754951477, -0.01437410619109869, -0.019798768684267998, -0.030573097988963127, 0.0017467663856223226, -0.0031951137352734804, 0.010030626319348812, -0.03797263652086258, -0.006424600258469582, -0.024160997942090034, -0.0070433118380606174, 0.014861575327813625, -0.00900568999350071, 0.012424226850271225, -0.011280547827482224, 0.021248677745461464, -0.00012137916928622872, -0.008461973629891872, 0.008524470031261444, -0.010661836713552475, 0.020786207169294357, 0.028298240154981613, -0.0230860635638237, -0.0037435172125697136, -0.019036315381526947, -0.019623778760433197, 0.03427286818623543, 0.03522280976176262, -6.142196070868522e-05, -0.025273427367210388, -0.002368602668866515, -0.02403600513935089, -0.0007964348187670112, 0.023186057806015015, -0.03052310086786747, 0.02717331051826477, -0.009111933410167694, -0.0018405105220153928, 0.024023504927754402, -0.0012936695711687207, -0.027823269367218018, 0.02153616026043892, 0.0654459297657013, -0.029573161154985428, 0.01241172756999731, -0.02134867198765278, -0.022411106154322624, -0.01662396639585495, -0.023823518306016922, -0.013349169865250587, 0.03597276285290718, -0.03029811568558216, -0.019886262714862823, 0.00649959547445178, 0.0219111368060112, 0.0293731726706028, -0.017173931002616882, 0.006568341515958309, -0.03389789164066315, -0.004074746742844582, 0.020823704078793526, -0.008636962622404099, 0.019248802214860916, 0.015774019062519073, -0.021673651412129402, 0.026148373261094093, 0.03992251679301262, -0.03362290933728218, 0.0195612832903862, 0.01978626847267151, -0.006705832667648792, -0.023123562335968018, -0.005352791864424944, 0.03992251679301262, 0.025973385199904442, -0.012355481274425983, 0.018861327320337296, -0.03462284803390503, -0.008743206039071083, -0.01104931253939867, -0.011018064804375172, -0.012230489403009415, -0.0029826269019395113, 0.012417977675795555, 0.01678645610809326, -0.019611280411481857, 0.009980629198253155, -0.016961444169282913, 0.03777265176177025, 0.021761145442724228, -0.00351853109896183, -0.020586218684911728, -0.036297742277383804, 0.0030826206784695387, 0.017073938623070717, 0.02917318604886532, -0.0146865863353014, -0.049796901643276215, -0.0018498849822208285, 0.02267359010875225, 0.012824201956391335, -0.00879320316016674, -0.018048876896500587, 0.012555468827486038, 0.061496175825595856, 0.01250547170639038, -0.03564778342843056, -0.04987189918756485, 0.017286425456404686, -0.02914818748831749, 0.022236116230487823, -0.0054777842015028, 0.02153616026043892, 0.050746843218803406, 0.010886822827160358, 0.013636651448905468, 0.0035154062788933516, -0.019873764365911484, -0.06204614043235779, 0.009399414993822575, 0.03857260197401047, 0.01527404971420765, -0.012911696918308735, 0.00851197075098753, -0.011086810380220413, -0.00479657668620348, -0.02814824879169464, 0.02092369832098484, -0.02344854176044464, -0.001214768155477941, 0.0008733831928111613, -0.019411291927099228, 0.004777827765792608, 0.024985944852232933, 0.028273241594433784, -0.010824326425790787, -0.03239798545837402, -0.017511410638689995, 0.007012063637375832, -0.026948323473334312, -0.03154803812503815, 0.0015295923221856356, 0.007955755107104778, -0.03722268342971802, 0.02502344362437725, 0.014574093744158745, 0.02173614874482155, 0.009849387221038342, -0.010811827145516872, -0.019998755306005478, 0.01918630674481392, 0.0122179901227355, -0.035297803580760956, 0.008924445137381554, 0.016736458986997604, -0.003377914894372225, 0.014149120077490807, 0.020486226305365562, 0.0135116595774889, 0.024948447942733765, -0.03224799409508705, -0.021236179396510124, 0.04087245836853981, 0.008149493485689163, 0.010993066243827343, -0.05119681358337402, -0.0006960504688322544, 0.024123499169945717, 0.0005210613599047065, 0.03957253694534302, 0.0172114297747612, 0.003287295578047633, 0.044722218066453934, 0.006183990277349949, -0.001113993232138455, 0.036297742277383804, 0.0033185435459017754, 0.010080622509121895, -0.004837199114263058, 0.060746222734451294, 0.025923388078808784, -0.006430849898606539, -0.025173433125019073, 0.04557216539978981, 0.014861575327813625, 0.008174491114914417, -0.017723897472023964, 0.01992376148700714, 0.013236676342785358, -0.024973446503281593, -0.026273366063833237, 0.0010843075579032302, 0.02309856377542019, -0.01604900136590004, 0.0015413103392347693, -0.010986816138029099, 0.0015381855191662908, 0.02034873329102993, -0.01604900136590004, -0.01702394150197506, 0.06574591249227524, -0.006599589250981808, -0.004390351939946413, 0.013436663895845413, -0.030473103746771812, 0.004584089852869511, -0.012611715123057365, -0.03287295624613762, 0.027248304337263107, -0.00012606637028511614, 0.0015530283562839031, 0.01361165288835764, 0.0170364398509264, -0.02717331051826477, 0.023811018094420433, 0.04522218555212021, 0.017748896032571793, 0.036672718822956085, 0.016273988410830498, 0.009586903266608715, -0.030573097988963127, 0.016673963516950607, 0.00889319647103548, 0.003477908670902252, 0.016873950138688087, -0.03814762830734253, 0.01243672613054514, 0.017461413517594337, 0.00900568999350071, 0.006855823565274477, 0.002084245439618826, -0.01978626847267151, 0.02757328562438488, 0.007155804894864559, -0.0020904948469251394, 0.0283732358366251, -0.015399042516946793, 0.005602776538580656, -0.018323859199881554, 0.010811827145516872, 0.011599278077483177, -0.01184926275163889, 0.044097255915403366, 7.089402788551524e-05, 0.023748522624373436, 0.019036315381526947, -0.024723462760448456, 0.033547911792993546, -0.005240298807621002, -0.023436041548848152, 0.007887009531259537, 0.006680834572762251, -0.016298986971378326, 0.017298923805356026, 0.03192301467061043, 0.018486350774765015, 0.013474161736667156, -0.007318294607102871, -0.010711833834648132, 0.012686710804700851, -0.010543094016611576, 0.029498165473341942, -0.007899508811533451, -0.022173620760440826, 0.01419911626726389, 0.03812262788414955, -0.02128617651760578, -0.017886387184262276, -0.031373050063848495, -0.018248865380883217, 0.006193364504724741, -0.004718456417322159, 0.005352791864424944, -0.0028107627294957638, 0.0071683041751384735, -0.0028248243033885956, -0.022611092776060104, -0.0359477624297142, -0.00851197075098753, -0.027848267927765846, -0.0006362885469570756, 0.005109057296067476, 0.017561407759785652, 0.021748647093772888, 0.032922953367233276, 0.026523349806666374, -0.011699272319674492, -0.02697332203388214, 0.013499160297214985, 0.001924880314618349, -0.01799887977540493, -0.007880759425461292, -0.005055935587733984, 0.0073870406486094, 0.012742957100272179, -0.0034997821785509586, 0.0033997884020209312, 0.0011264923959970474, -0.010611839592456818, -0.007230800110846758, 0.007874510250985622, -0.0191488079726696, 0.006593340076506138, -0.035122815519571304, -0.010574341751635075, -0.02502344362437725, 0.005205926019698381, 0.013336670584976673, 0.046047136187553406, 0.012699210084974766, -0.051946770399808884, -0.0015053751412779093, -0.014149120077490807, 0.025285927578806877, 0.004509094636887312, 0.02092369832098484, -0.02894819900393486, 0.005374665837734938, -0.001142897643148899, -0.0059558795765042305, 0.0032716714777052402, -0.019873764365911484, 0.011668024584650993, 0.01123680081218481, -0.013661650009453297, -0.03539779782295227, -0.0014194429386407137, 0.0065808407962322235, 0.023523537442088127, 0.01289919763803482, -0.010986816138029099, -0.0025451541878283024, 0.005221550352871418, 0.07639525085687637, -0.020286237820982933, -0.0283732358366251, -0.009605652652680874, 0.003759141080081463, -0.0011311796260997653, 0.005005938466638327, -0.001140554086305201, -0.010943069122731686, 0.016536470502614975, 0.0073745413683354855, -0.014349107630550861, 0.024111000820994377, -0.006649586372077465, 0.02328605204820633, 0.0013585092965513468, -0.010443100705742836, -0.04384727403521538, -0.016173994168639183, -0.030048130080103874, 0.03209800273180008, -0.003734142752364278, 0.004171615466475487, -0.02506094053387642, 0.0013975693145766854, -0.000719486502930522, -0.03694770112633705, -0.0037216434720903635, 0.025073440745472908, 0.054296623915433884, 0.0031201185192912817, 0.027348298579454422, -0.0058277626521885395, -0.0004081777296960354, -0.030248118564486504, -0.028298240154981613, -0.014711584895849228, 0.0060339998453855515, -0.0037372675724327564, -0.04442223533987999, -0.026498351246118546, 0.045122191309928894, 0.003806013148277998, 0.014161619357764721, -0.005005938466638327, -0.012043001130223274, 0.00597150344401598, 0.035297803580760956, -0.002218612004071474, 0.008755705319344997, -0.0032935452181845903, 0.004030999261885881, -0.012786704115569592, -0.0003781014820560813, 0.021236179396510124, -0.04007250815629959, -0.012874199077486992, 0.00586213544011116, 0.03919756039977074, 0.015786517411470413, 0.01329917274415493, -0.013736645691096783, -0.0034435358829796314, -0.0010639963438734412, 0.019798768684267998, -0.03517281264066696, 0.002218612004071474, 0.023323548957705498, -0.00869945902377367, 0.0010015001753345132, -0.013549157418310642, 0.010993066243827343, -0.01621149107813835, 0.014861575327813625, -0.01311168447136879, -0.0096931466832757, 0.02562340535223484, -0.007868260145187378, 0.00626211054623127, -0.030948074534535408, 0.00459971372038126, -0.0070808096788823605, 0.00783076323568821, 0.010986816138029099, 0.015911510214209557, 0.0037622659001499414, -0.009930632077157497, -0.06539592891931534, 0.006077746860682964, -0.0007745611947029829, -0.0019889387767761946, -0.009186928160488605, 0.021198680624365807, 0.05559654161334038, 0.0031654280610382557, -0.0017217679414898157, 0.005493408069014549, 0.00420911330729723, 0.007324544247239828, -0.02912318892776966, -0.004440348595380783, 0.004437224008142948, 0.013774143531918526, 0.022773582488298416, 0.03302294388413429, -0.01163052674382925, -0.04649710655212402, 0.004712206777185202, -0.007493284065276384, 0.025160934776067734, 0.008499471470713615, 0.0020248740911483765, 0.0476970337331295, -0.05924631282687187, -0.0060808719135820866, 0.0010257174726575613, -0.014086623676121235, 0.004393476527184248, -0.020998694002628326, 0.03834761306643486, 0.024548472836613655, -0.004955941811203957, 0.013761644251644611, 0.00066558358957991, -0.023161059245467186, 0.0070058139972388744, 0.013236676342785358, 0.03464784473180771, 0.01447409950196743, 0.007093308959156275, -0.0009429100900888443, -0.023323548957705498, -0.009524407796561718, 0.014886573888361454, 0.01761140488088131, -0.003943504765629768, 0.007012063637375832, -0.030598096549510956, 0.020873701199889183, -0.009511908516287804, 0.02149866335093975, 0.04109744355082512, 0.010843075811862946, 0.0005034842761233449, 0.038247622549533844, -0.00832448247820139, -0.009230676107108593, 0.002252984791994095, 0.002682645572349429, -0.01796138286590576, 0.013474161736667156, 0.004318481311202049, 0.0027763897087424994, -0.009836887940764427, -0.021411167457699776, 0.031023070216178894, -0.03972252830862999, -0.03734767809510231, -0.01839885488152504, 0.005959004163742065, 0.03934755176305771, -0.014211615547537804, -0.008624463342130184, 0.023898513987660408, -0.01361165288835764, -0.020886201411485672, -0.05259672924876213, 0.030473103746771812, -0.004146616905927658, 0.00947441067546606, 0.009024438448250294, -0.021198680624365807, 0.013574155978858471, 0.020598718896508217, 0.0056215254589915276, 0.0002634601842146367, -0.014536595903337002, 0.04832199588418007, 0.009080684743821621, 0.01125555019825697, 0.038447607308626175, -0.016398979350924492, -0.013286673463881016, 0.007674522697925568, 0.0037497668527066708, 0.019511286169290543, 0.00188269536010921, -0.03672271594405174, 0.0036685217637568712, -0.0046153380535542965, 0.0007769048097543418, 0.006887071765959263, 0.006177740637212992, -0.0073370435275137424, 0.007555780000984669, 0.05019687861204147, -0.029798146337270737, -0.03739767521619797, -0.030048130080103874, -0.027273302897810936, -0.022948572412133217, -0.020611217245459557, 0.03729768097400665, 0.014049125835299492, 0.013449163176119328, -0.013624152168631554, -0.014311609789729118, -0.011436788365244865, -0.02364852838218212, 0.0121304951608181, 0.029673153534531593, -0.014349107630550861, 0.005234049633145332, -0.02188613824546337, 0.02013624645769596, -0.01977377012372017, 0.02031123638153076, 0.005365291144698858, -0.029098190367221832, 0.009793140925467014, 0.010568092577159405, -0.01026811171323061, -0.0031904266215860844, -0.024785958230495453, -0.017723897472023964, 0.00821198895573616, 0.017561407759785652, -0.031748026609420776, -0.01302418950945139, -0.01554903294891119, 0.01719892956316471, -0.0160365030169487, 0.0071683041751384735, -0.016736458986997604, 0.004518468864262104, 0.02228611335158348, -0.02834823727607727, -0.009793140925467014, 0.04637211561203003, -0.011911759153008461, -0.031598035246133804, 0.005555904470384121, -0.02012374810874462, 0.0324229821562767, -0.00833073165267706, 0.00689957058057189, 0.014611590653657913, 0.01104931253939867, -0.015736520290374756, -0.024373482912778854, 0.02227361500263214, 0.018136370927095413, 0.008493221364915371, -0.026848329231142998, -0.0032622970174998045, -0.0034185373224318027, 0.02033623494207859, -0.017898887395858765, -0.016523972153663635, -0.03419787436723709, -0.020811205729842186, -0.018298862501978874, -0.03319793567061424, -0.03849760442972183, 0.0179738812148571, -0.022186119109392166, 0.006780828349292278, 0.008674460463225842, -0.01876133307814598, 0.022198619320988655, -0.010193116031587124, -0.022186119109392166, 0.0037747651804238558, 0.005743392743170261, -0.03474783897399902, 0.006318356841802597, -0.0014577218098565936, -0.021086188033223152, 0.010993066243827343, -0.009736894629895687, -0.023348547518253326, -0.02657334692776203, -0.0066683352924883366, -0.00675582978874445, 0.00757452892139554, 0.0017967632738873363, -0.008649461902678013, 0.015486536547541618, -0.002724830526858568, -0.017548907548189163, -0.0003376743115950376, -0.0022061127237975597, -0.003656022483482957, -0.017661402001976967, 0.024323487654328346, 0.029298176988959312, -0.03857260197401047, 0.006705832667648792, -0.039397548884153366, -0.020636215806007385, -0.02839823253452778, 0.019411291927099228, -0.007943255826830864, -0.02637336030602455, 0.005421537905931473, 0.00572464382275939, 0.02036123350262642, -0.00978689081966877, 0.0002497891546227038, 0.02582339383661747, -0.018323859199881554, 0.024435980245471, 0.002526405267417431, 0.028248243033885956, -0.01644897647202015, 0.032797958701848984, 0.008368229493498802, -0.00901193916797638, 0.0009108808590099216, -0.0071683041751384735, 0.00888694729655981, 0.01076808013021946, -0.0281982459127903, -0.007137055974453688, 0.09769392013549805, -0.02579839527606964, -0.04052247852087021, 0.01602400280535221, 0.005577777978032827, 0.05184677615761757, 0.0043622287921607494, 0.029498165473341942, 0.0006038686842657626, -0.017648901790380478, 0.020598718896508217, 0.015124059282243252, -0.018323859199881554, 0.0059152571484446526, -0.03797263652086258, 0.0022108000703155994, -0.022086126729846, 0.0207362100481987, 0.012874199077486992, -0.008043250069022179, 0.025073440745472908, 0.025773396715521812, -0.009030688554048538, -0.008218239061534405, -0.004299732390791178, -0.0015702148666605353, -0.017761394381523132, -0.026298364624381065, 0.036447733640670776, -0.002882633125409484, 0.012005503289401531, 0.030023131519556046, 0.035122815519571304, -0.02311106212437153, -0.027448292821645737, 0.017573906108736992, 0.007599527481943369, 0.018648840487003326, 0.04009750485420227, -0.009311920963227749, -0.027723275125026703, 0.022248616442084312, 0.014861575327813625, 0.006180865690112114, 0.0193862933665514, -0.003927880432456732, -0.012205490842461586, -0.013311672024428844, -0.02503594197332859, -0.019098812714219093, 0.00694331806153059, 0.016311485320329666, -0.051171816885471344, -0.013436663895845413, -0.0033997884020209312, -0.019048815593123436, 0.015924008563160896, 0.004193488974124193, 0.008630713447928429, -0.011743019334971905, 0.007455786224454641, 0.019098812714219093, -0.0028341987635940313, -0.005046560894697905, -0.011080560274422169]\n"
     ]
    }
   ],
   "source": [
    "# 调用 embedding API 获取文本的向量表示\n",
    "response = client.embeddings.create(\n",
    "    input=\"测试文本\",  # 输入文本\n",
    "    model=\"text-embedding-3-small\"  # 选择 Embedding 模型\n",
    ")\n",
    "# 打印返回的 embedding 向量\n",
    "print(response.data[0].embedding)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ef234216-cf6d-4dc9-8974-f27591ec9734",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:23:59.669463Z",
     "iopub.status.busy": "2025-07-23T05:23:59.668459Z",
     "iopub.status.idle": "2025-07-23T05:23:59.679821Z",
     "shell.execute_reply": "2025-07-23T05:23:59.679238Z",
     "shell.execute_reply.started": "2025-07-23T05:23:59.669463Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1536"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(response.data[0].embedding)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ffef676-ce1b-4237-bbf5-a74f3c8572ec",
   "metadata": {},
   "source": [
    "- text-embedding-3-small 生成的向量长度为 1536，text-embedding-3-large 的向量长度为 3072。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c86b9aa-85a4-4e25-93fe-2366f7f0af4a",
   "metadata": {},
   "source": [
    "- 欧氏距离与余弦相似度计算公式\n",
    "  假设现有a、b两个向量：\n",
    "$$\\vec{a} = [a_1, a_2, a_3, ...]$$\n",
    "$$\\vec{b} = [b_1, b_2, b_3, ...]$$\n",
    "余弦相似度计算公式为：\n",
    "$$\\text{Cosine Similarity} (\\vec{a}, \\vec{b}) = \\frac{\\vec{a} \\cdot \\vec{b}}{\\|\\vec{a}\\| \\|\\vec{b}\\|}$$\n",
    "---\n",
    "- $\\vec{a} \\cdot \\vec{b}$表示向量 $\\vec{a}$ 和向量 $\\vec{b}$ 的点积。\n",
    "\n",
    "- $\\|\\vec{a}\\|$ 和 $\\|\\vec{b}\\|$ 分别是向量 $\\vec{a}$ 和 $\\vec{b}$ 的模（长度）。\n",
    "\n",
    "- 点积 (Dot Product) 定义为：$\\vec{a} \\cdot \\vec{b} = a_1b_1 + a_2b_2 + \\ldots + a_nb_n$\n",
    "\n",
    "- 向量的模 (Magnitude) 定义为：\n",
    "$$\\|\\vec{a}\\| = \\sqrt{a_1^2 + a_2^2 + \\ldots + a_n^2}$$\n",
    "$$\\|\\vec{b}\\| = \\sqrt{b_1^2 + b_2^2 + \\ldots + b_n^2}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "3ac9f055-d5c0-48ab-b187-5f749fabbe32",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:27:17.935672Z",
     "iopub.status.busy": "2025-07-23T05:27:17.935672Z",
     "iopub.status.idle": "2025-07-23T05:27:18.245085Z",
     "shell.execute_reply": "2025-07-23T05:27:18.244080Z",
     "shell.execute_reply.started": "2025-07-23T05:27:17.935672Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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atVk/fr10WrCfvjhBylfvrw5rp5HbHbu3ClVqlQxx7r77rtlxYoVEqgIWgAA2ECKFCmkTZs2JmhpjZHLl19+KdevX5cmTZrI3r17TfOi/l+bEWfOnGkCT7du3dz76zGmT58uH374oezYsUM+++wzSZcunQleX3/9tdln165dcvjwYfnggw/M/d69e5vHJk+eLJs2bTLhrG7dunLy5Emvc+zTp4+8++675rhx1a716tVLevbsKZs3b5bKlSubZtHIyEgJRAQtAABs4plnnjFhyrMGaOLEidK4cWO54447TMhp2bKldO/eXe68805Ta6SBasqUKXL58mXZvXu3zJo1SyZMmCBPPPGEFC5cWB5++GFp1qyZJE+eXDJnzmyOqTVXOXPmNMfU2rBPP/1Uhg8fLo888oiULFlSxo4da2rMtCbM06BBg6R27dpSpEgR97FiosFPw2CJEiXMsfX7+B4rUKTw9wkAAICkUbx4cROeNCjVqFFD9uzZYzrCL1myxDyutVh6mzp1qvs5Wvt148YN2bdvn/z6668mUFWvXj3e33Pv3r0SFRUlVatWdW8LDw+XihUrmporTxUqVIjXMbUWy7OmTp/ne6xAQY0WAAA26xSvzXjnzp0ztVlae+QKTtpfq3Pnzmb6BNdt69at8scff5j9tBbKSmnTppVQE1RB66effjLtsLlz5zad5ubMmRPn/rNnzzZVkNr5L0OGDCYBL1y40GufAQMGmGN53jTxAwAQipo2bSrJkiWTadOmmSZBbU7Uv31KRx7+/vvvpg+V7y1lypRSunRpU7sVW+dz3Udpny+XIkWKmO2rVq1yb9MaLu0Mr82It+KXX35x/19HJ27cuNE0IwaioApa2s5bpkwZGT16dLyDmQat+fPnm4ugoyE0qGnnOU+lSpUynfZct7hGOgAAEMy047r2qerbt6/5m+caGejqtL569WrTB0prs7Qma+7cue7O8AULFpS2bduacKaVHdqcqCMGtd+WKlCggAlt3333nRw/ftzUkGktVZcuXUwH9gULFpgg17FjR7l48aKpXbsVmgO++eYbM/rw+eefl1OnTplzCkRB1UdLO9HpLb7ef/99r/vvvPOOecN8++23JrV7tu9qpz0AAOxAA452Hq9fv75pJdJaKqUj/bS26vXXXzdTPGj/LK2R0mDmop3PX3vtNenatasZ6Zc/f35z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GjVEtm4lZAFA0Aat0aNHS8GCBSV16tRSqVIlWbduXaz7jh07Vh544AHJlCmTudWqVSva/u3atZOwsDCvW7169ZKgJEBwatfOuS7h1aveTYX9+4v8+KNIrlz+PDsACCxBFbRmzpwpPXr0kP79+8umTZukTJkyUrduXTl27FiM+y9fvlyefvppWbZsmaxZs0by5csnderUkUOHDnntp8Hq8OHD7tv06dOTqERA8NClctScOd7bc+Z0BqwBA0SSJ/fLqQFAwAqqoDVy5Ejp2LGjtG/fXkqWLCljxoyRNGnSyIQJE2Lcf+rUqdK1a1cpW7asFC9eXMaNGyc3btyQJUuWeO2XKlUqyZkzp/umtV8A/msq/OCDmJsDa9d2jip86CF/nBkABL6g6Qx/9epV2bhxo/Tt29e9LVmyZKY5UGur4uPixYsSFRUlmTNnjlbzlT17dhOwHnroIXnrrbcki07+E4srV66Ym8tZHXolYo6tN7twldVOZZaICImKiDD/jdL2shAv+6lTzmbC77/XojvLql+TJRN5/XWRHj305zC0XwZbvs8pN+W2iagkKG+Yw6GfVwPfP//8I3ny5JHVq1dL5cqV3dt79+4tK1askLVr1970GFq7tXDhQtm+fbvp46VmzJhhasUKFSoke/fulddee03SpUtnwlvyWNpBBgwYIAMHDoy2fdq0aeZYAAAg8GkFTIsWLeTMmTOSIUMGe9do3a53333XhCqtvXKFLNW8eXP3/0uXLi333HOPFClSxOz38MMPx3gsrVXTvmKeNVra/6tmzZpx1oSF4ieBxYsXS+3atSU8PFxCnnZOatvW1GgtnjBBap88KeGtWkmouXFDB504+1xdu/bfdq3JmjBhsZQvX1ty5LDB9bbr+/xflJty20FkZKTl3yNoglbWrFlNDdPRo0e9tut97VcVlxEjRpig9eOPP5ogFZfChQub77Vnz55Yg5b26dKbL31z2ukNastyeyzgF+5whFy5T5xwjirUpkJPWrk7aJDz/xqyQq3c8WGr97kHym0vdit3eBKUNWg6w6dMmVLKly/v1ZHd1bHdsynR17Bhw2Tw4MGyYMECqVChwk2/z8GDB03CzcUYddjMypUiZctGD1n58jkXg37pJX+dGQAEr6AJWkqb63RurMmTJ8uOHTukS5cucuHCBTMKUbVp08ars/zQoUPlzTffNKMS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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# 创建两个向量\n",
    "a = np.array([0, 1])\n",
    "b = np.array([1, 1])\n",
    "\n",
    "# 计算两个向量的余弦相似度\n",
    "cosine_similarity = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))\n",
    "\n",
    "# 绘制向量\n",
    "plt.quiver(0, 0, a[0], a[1], angles='xy', scale_units='xy', scale=1, color='r')\n",
    "plt.quiver(0, 0, b[0], b[1], angles='xy', scale_units='xy', scale=1, color='b')\n",
    "\n",
    "# 设置图表属性\n",
    "plt.xlim(-0.5, 1.5)\n",
    "plt.ylim(-0.5, 1.5)\n",
    "plt.grid()\n",
    "plt.title(f'Cosine Similarity: {cosine_similarity:.2f}')\n",
    "plt.xlabel('X axis')\n",
    "plt.ylabel('Y axis')\n",
    "\n",
    "# 添加图例\n",
    "plt.legend(['Vector a', 'Vector b'])\n",
    "\n",
    "# 显示图表\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8de67e65-3fc5-477b-a4a9-a597cd21ad8e",
   "metadata": {},
   "source": [
    "- 红色向量代表$\\vec{a}$ ，蓝色向量代表 $\\vec{b}$ 。它们之间的夹角表示了两个向量的余弦相似度。余弦相似度是通过计算两个向量的点积并除以它们各自的范数（即长度）来得到的。在这个示例中，这两个向量的余弦相似度大约为 0.71，意味着它们在方向上有一定程度的相似性。这个值越接近 1，表示两个向量的方向越相似。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "c9caf5cb-0c78-4d7a-b802-fcf5ec77ea4d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:30:45.824285Z",
     "iopub.status.busy": "2025-07-23T05:30:45.824285Z",
     "iopub.status.idle": "2025-07-23T05:30:48.122301Z",
     "shell.execute_reply": "2025-07-23T05:30:48.121336Z",
     "shell.execute_reply.started": "2025-07-23T05:30:45.824285Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.7661800739633343\n",
      "0.7224817543294454\n",
      "0.6315437708887437\n"
     ]
    }
   ],
   "source": [
    "def cosine_sim(vector1: List[float], vector2: List[float]) -> float:\n",
    "    \"\"\"\n",
    "    计算两个向量之间的余弦相似度\n",
    "    \"\"\"\n",
    "    dot_product = np.dot(vector1, vector2)\n",
    "    magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)\n",
    "    if not magnitude:\n",
    "        return 0\n",
    "    return dot_product / magnitude\n",
    "\n",
    "text1 = '我喜欢吃苹果'\n",
    "text2 = \"苹果是我最喜欢吃的水果\"\n",
    "text3 = \"我喜欢用苹果手机\"\n",
    "\n",
    "vector1 = client.embeddings.create(\n",
    "    input=text1,  \n",
    "    model=\"text-embedding-3-large\"  \n",
    ").data[0].embedding\n",
    "\n",
    "vector2 = client.embeddings.create(\n",
    "    input=text2,  \n",
    "    model=\"text-embedding-3-large\"  \n",
    ").data[0].embedding\n",
    "\n",
    "vector3 = client.embeddings.create(\n",
    "    input=text3,  \n",
    "    model=\"text-embedding-3-large\"  \n",
    ").data[0].embedding\n",
    "\n",
    "\n",
    "print(np.float64(cosine_sim(vector1, vector2)))\n",
    "\n",
    "print(np.float64(cosine_sim(vector1, vector3)))\n",
    "\n",
    "print(np.float64(cosine_sim(vector2, vector3)))\n",
    "\n",
    "'''\n",
    "0.7661800739633343\n",
    "0.7224817543294454\n",
    "0.6315437708887437\n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7dd9da17-6b40-45cd-aa2d-a0ea883f4a60",
   "metadata": {},
   "source": [
    "- 为了实现 RAG 模型的功能，我们首先需要一个向量化（Embedding）模块。向量化是 RAG 的基础，它的作用是将文档片段转化为向量表示，便于后续的检索操作。在这个过程中，我们将实现一个向量化类，用来将文本片段映射成向量。\n",
    "\n",
    "- 为了便于扩展和未来可能使用不同的模型，我们首先编写一个 Embedding 基类。该基类定义了获取文本向量表示的方法，同时包含一个计算两个向量之间余弦相似度的功能。这样，如果我们未来使用不同的向量化模型，只需继承该基类并重写向量获取的逻辑，而不需要重复编写相似度计算部分。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "9a89f367-d42f-4d48-80f1-5ea4413128d3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:36:33.936684Z",
     "iopub.status.busy": "2025-07-23T05:36:33.935660Z",
     "iopub.status.idle": "2025-07-23T05:36:33.945478Z",
     "shell.execute_reply": "2025-07-23T05:36:33.944917Z",
     "shell.execute_reply.started": "2025-07-23T05:36:33.936684Z"
    }
   },
   "outputs": [],
   "source": [
    "class BaseEmbeddings:\n",
    "    \"\"\"\n",
    "    向量化的基类，用于将文本转换为向量表示。不同的子类可以实现不同的向量获取方法。\n",
    "    \"\"\"\n",
    "    def __init__(self, path: str, is_api: bool) -> None:\n",
    "        \"\"\"\n",
    "        初始化基类。\n",
    "        \n",
    "        参数：\n",
    "        path (str) - 如果是本地模型，path 表示模型路径；如果是API模式，path可以为空\n",
    "        is_api (bool) - 表示是否使用API调用，如果为True表示通过API获取Embedding\n",
    "        \"\"\"\n",
    "        self.path = path\n",
    "        self.is_api = is_api\n",
    "    \n",
    "    def get_embedding(self, text: str, model: str) -> List[float]:\n",
    "        \"\"\"\n",
    "        抽象方法，用于获取文本的向量表示，具体实现需要在子类中定义。\n",
    "        \n",
    "        参数：\n",
    "        text (str) - 需要转换为向量的文本\n",
    "        model (str) - 所使用的模型名称\n",
    "        \n",
    "        返回：\n",
    "        list[float] - 文本的向量表示\n",
    "        \"\"\"\n",
    "        raise NotImplementedError\n",
    "    \n",
    "    @classmethod\n",
    "    def cosine_similarity(cls, vector1: List[float], vector2: List[float]) -> float:\n",
    "        \"\"\"\n",
    "        计算两个向量之间的余弦相似度，用于衡量它们的相似程度。\n",
    "        \n",
    "        参数：\n",
    "        vector1 (list[float]) - 第一个向量\n",
    "        vector2 (list[float]) - 第二个向量\n",
    "        \n",
    "        返回：\n",
    "        float - 余弦相似度值，范围从 -1 到 1，越接近 1 表示向量越相似\n",
    "        \"\"\"\n",
    "        dot_product = np.dot(vector1, vector2)  # 向量点积\n",
    "        magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)  # 向量的模\n",
    "        if not magnitude:\n",
    "            return 0\n",
    "        return dot_product / magnitude  # 计算余弦相似度"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71d49556-1305-4f5d-8ba1-72a6f78214c1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:33:02.632889Z",
     "iopub.status.busy": "2025-07-23T05:33:02.632889Z",
     "iopub.status.idle": "2025-07-23T05:33:02.641088Z",
     "shell.execute_reply": "2025-07-23T05:33:02.640130Z",
     "shell.execute_reply.started": "2025-07-23T05:33:02.632889Z"
    }
   },
   "source": [
    "- 在这个基类基础上，可以通过继承它来实现具体的模型。例如，使用 OpenAI 的 API 来生成文本的向量表示，只需重写 `get_embedding` 方法即可。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "82dae4f7-9747-4da0-9ab8-7a401316287a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:36:35.884064Z",
     "iopub.status.busy": "2025-07-23T05:36:35.884064Z",
     "iopub.status.idle": "2025-07-23T05:36:35.894265Z",
     "shell.execute_reply": "2025-07-23T05:36:35.893696Z",
     "shell.execute_reply.started": "2025-07-23T05:36:35.884064Z"
    }
   },
   "outputs": [],
   "source": [
    "# 这样设计的结构让我们可以轻松替换或扩展向量化模型，而不需要改变整体框架。\n",
    "class OpenAIEmbedding(BaseEmbeddings):\n",
    "    \"\"\"\n",
    "    使用 OpenAI 的 Embedding API 来获取文本向量的类，继承自 BaseEmbeddings。\n",
    "    \"\"\"\n",
    "    def __init__(self, path: str = '', is_api: bool = True) -> None:\n",
    "        \"\"\"\n",
    "        初始化类，设置 OpenAI API 客户端，如果使用的是 API 调用。\n",
    "        \n",
    "        参数：\n",
    "        path (str) - 本地模型的路径，使用API时可以为空\n",
    "        is_api (bool) - 是否通过 API 获取 Embedding，默认为 True\n",
    "        \"\"\"\n",
    "        super().__init__(path, is_api)\n",
    "        if self.is_api:\n",
    "            # 初始化 OpenAI API 客户端\n",
    "            from openai import OpenAI\n",
    "            self.client = OpenAI()\n",
    "            self.client.api_key = os.getenv(\"OPENAI_API_KEY\")  # 从环境变量中获取 API 密钥\n",
    "            self.client.base_url = os.getenv(\"OPENAI_BASE_URL\")  # 从环境变量中获取 API 基础URL\n",
    "    \n",
    "    def get_embedding(self, text: str, model: str = \"text-embedding-3-large\") -> List[float]:\n",
    "        \"\"\"\n",
    "        使用 OpenAI 的 Embedding API 获取文本的向量表示。\n",
    "        \n",
    "        参数：\n",
    "        text (str) - 需要转化为向量的文本\n",
    "        model (str) - 使用的 Embedding 模型名称，默认为 'text-embedding-3-large'\n",
    "        \n",
    "        返回：\n",
    "        list[float] - 文本的向量表示\n",
    "        \"\"\"\n",
    "        if self.is_api:\n",
    "            # 去掉文本中的换行符，保证输入格式规范\n",
    "            text = text.replace(\"\\n\", \" \")\n",
    "            # 调用 OpenAI API 获取文本的向量表示\n",
    "            return self.client.embeddings.create(input=[text], model=model).data[0].embedding\n",
    "        else:\n",
    "            raise NotImplementedError  # 如果不是 API 模式，这里未实现本地模型的处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "e5830917-42ec-410e-b414-a080d4eb4791",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:39:46.365259Z",
     "iopub.status.busy": "2025-07-23T05:39:46.365259Z",
     "iopub.status.idle": "2025-07-23T05:39:49.865039Z",
     "shell.execute_reply": "2025-07-23T05:39:49.864483Z",
     "shell.execute_reply.started": "2025-07-23T05:39:46.365259Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "文本向量的长度为： 3072\n",
      "两段文本的余弦相似度为: 0.7662107582385512\n"
     ]
    }
   ],
   "source": [
    "# 初始化 Embedding 模型\n",
    "embedding_model = OpenAIEmbedding()\n",
    "\n",
    "# 输入需要获取向量表示的文本\n",
    "text = \"愿世界充满善良和和平\"\n",
    "\n",
    "# 获取文本的向量表示\n",
    "embedding_vector = embedding_model.get_embedding(text, model=\"text-embedding-3-large\")\n",
    "print(\"文本向量的长度为：\",len(embedding_vector))\n",
    "# print(\"文本的向量表示为：\", embedding_vector)\n",
    "\n",
    "vector1 = embedding_model.get_embedding(text1)\n",
    "vector2 = embedding_model.get_embedding(text2)\n",
    "similarity = BaseEmbeddings.cosine_similarity(vector1, vector2)\n",
    "print(f\"两段文本的余弦相似度为: {similarity}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a321d629-c25d-4833-8477-6cb1eef15fde",
   "metadata": {},
   "source": [
    "# 文档加载与切分模块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "07f8b503-7913-4ea1-aae9-38cc1a6a4c4d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:45:56.992810Z",
     "iopub.status.busy": "2025-07-23T05:45:56.992810Z",
     "iopub.status.idle": "2025-07-23T05:45:57.003723Z",
     "shell.execute_reply": "2025-07-23T05:45:57.002685Z",
     "shell.execute_reply.started": "2025-07-23T05:45:56.992810Z"
    }
   },
   "outputs": [],
   "source": [
    "def read_file_content(cls, file_path: str):\n",
    "    # 根据文件扩展名选择读取方法\n",
    "    if file_path.endswith('.pdf'):\n",
    "        return cls.read_pdf(file_path)\n",
    "    elif file_path.endswith('.md'):\n",
    "        return cls.read_markdown(file_path)\n",
    "    elif file_path.endswith('.txt'):\n",
    "        return cls.read_text(file_path)\n",
    "    else:\n",
    "        raise ValueError(\"Unsupported file type\")\n",
    "\n",
    "def get_chunk(cls, text: str, max_token_len: int = 600, cover_content: int = 150):\n",
    "    chunk_text = []\n",
    "    curr_len = 0\n",
    "    curr_chunk = ''\n",
    "    lines = text.split('\\n')  # 以换行符为单位切分文本\n",
    "\n",
    "    for line in lines:\n",
    "        line = line.replace(' ', '')\n",
    "        line_len = len(enc.encode(line))  # 计算当前行的 Token 长度\n",
    "        if line_len > max_token_len:\n",
    "            print('warning line_len = ', line_len)\n",
    "        if curr_len + line_len <= max_token_len:\n",
    "            curr_chunk += line + '\\n'\n",
    "            curr_len += line_len + 1\n",
    "        else:\n",
    "            chunk_text.append(curr_chunk)\n",
    "            curr_chunk = curr_chunk[-cover_content:] + line\n",
    "            curr_len = line_len + cover_content\n",
    "\n",
    "    if curr_chunk:\n",
    "        chunk_text.append(curr_chunk)\n",
    "\n",
    "    return chunk_text"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28fb98e8-4b03-4e4f-bf09-d65d8944cc4a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:42:14.900466Z",
     "iopub.status.busy": "2025-07-23T05:42:14.899897Z",
     "iopub.status.idle": "2025-07-23T05:42:14.907692Z",
     "shell.execute_reply": "2025-07-23T05:42:14.907143Z",
     "shell.execute_reply.started": "2025-07-23T05:42:14.900466Z"
    }
   },
   "source": [
    "将文档按Token长度进行切分，设置一个最大的 Token 长度，然后按这个长度进行切分。在这个过程中，我们也会确保每个片段之间有一定的重叠，避免重要信息被切掉。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "fe22a6e4-cce1-4ea4-92e3-06317b66cc75",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:46:00.766697Z",
     "iopub.status.busy": "2025-07-23T05:46:00.766697Z",
     "iopub.status.idle": "2025-07-23T05:46:00.774908Z",
     "shell.execute_reply": "2025-07-23T05:46:00.774387Z",
     "shell.execute_reply.started": "2025-07-23T05:46:00.766697Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "enc = tiktoken.get_encoding(\"cl100k_base\")\n",
    "len(enc.encode(\"你好，好久不见！\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "ee7a14e3-63fe-4d30-bc0d-d125c01c3f3d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:46:01.526518Z",
     "iopub.status.busy": "2025-07-23T05:46:01.526518Z",
     "iopub.status.idle": "2025-07-23T05:46:01.547672Z",
     "shell.execute_reply": "2025-07-23T05:46:01.546162Z",
     "shell.execute_reply.started": "2025-07-23T05:46:01.526518Z"
    }
   },
   "outputs": [],
   "source": [
    "class ReadFiles:\n",
    "    \"\"\"\n",
    "    读取文件的类，用于从指定路径读取支持的文件类型（如 .txt、.md、.pdf）并进行内容分割。\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, path: str) -> None:\n",
    "        \"\"\"\n",
    "        初始化函数，设定要读取的文件路径，并获取该路径下所有符合要求的文件。\n",
    "        :param path: 文件夹路径\n",
    "        \"\"\"\n",
    "        self._path = path\n",
    "        self.file_list = self.get_files()  # 获取文件列表\n",
    "        self.enc = tiktoken.get_encoding(\"cl100k_base\")\n",
    "\n",
    "    def get_files(self):\n",
    "        \"\"\"\n",
    "        遍历指定文件夹，获取支持的文件类型列表（txt, md, pdf）。\n",
    "        :return: 文件路径列表\n",
    "        \"\"\"\n",
    "        file_list = []\n",
    "        for filepath, dirnames, filenames in os.walk(self._path):\n",
    "            # os.walk 函数将递归遍历指定文件夹\n",
    "            for filename in filenames:\n",
    "                # 根据文件后缀筛选支持的文件类型\n",
    "                if filename.endswith(\".md\"):\n",
    "                    file_list.append(os.path.join(filepath, filename))\n",
    "                elif filename.endswith(\".txt\"):\n",
    "                    file_list.append(os.path.join(filepath, filename))\n",
    "                elif filename.endswith(\".pdf\"):\n",
    "                    file_list.append(os.path.join(filepath, filename))\n",
    "        return file_list\n",
    "\n",
    "    def get_content(self, max_token_len: int = 600, cover_content: int = 150):\n",
    "        \"\"\"\n",
    "        读取文件内容并进行分割，将长文本切分为多个块。\n",
    "        :param max_token_len: 每个文档片段的最大 Token 长度\n",
    "        :param cover_content: 在每个片段之间重叠的 Token 长度\n",
    "        :return: 切分后的文档片段列表\n",
    "        \"\"\"\n",
    "        docs = []\n",
    "        for file in self.file_list:\n",
    "            content = self.read_file_content(file)  # 读取文件内容\n",
    "            # 分割文档为多个小块\n",
    "            chunk_content = self.get_chunk(content, max_token_len=max_token_len, cover_content=cover_content)\n",
    "            docs.extend(chunk_content)\n",
    "        return docs\n",
    "\n",
    "    @classmethod\n",
    "    def get_chunk(cls, text: str, max_token_len: int = 600, cover_content: int = 150):\n",
    "        \"\"\"\n",
    "        将文档内容按最大 Token 长度进行切分。\n",
    "        :param text: 文档内容\n",
    "        :param max_token_len: 每个片段的最大 Token 长度\n",
    "        :param cover_content: 重叠的内容长度\n",
    "        :return: 切分后的文档片段列表\n",
    "        \"\"\"\n",
    "        chunk_text = []\n",
    "        curr_len = 0\n",
    "        curr_chunk = ''\n",
    "        token_len = max_token_len - cover_content\n",
    "        lines = text.splitlines()  # 以换行符分割文本为行\n",
    "\n",
    "        for line in lines:\n",
    "            line = line.replace(' ', '')  # 去除空格\n",
    "            line_len = len(enc.encode(line))  # 计算当前行的 Token 长度\n",
    "            if line_len > max_token_len:\n",
    "                # 如果单行长度超过限制，将其分割为多个片段\n",
    "                num_chunks = (line_len + token_len - 1) // token_len\n",
    "                for i in range(num_chunks):\n",
    "                    start = i * token_len\n",
    "                    end = start + token_len\n",
    "                    # 防止跨单词分割\n",
    "                    while not line[start:end].rstrip().isspace():\n",
    "                        start += 1\n",
    "                        end += 1\n",
    "                        if start >= line_len:\n",
    "                            break\n",
    "                    curr_chunk = curr_chunk[-cover_content:] + line[start:end]\n",
    "                    chunk_text.append(curr_chunk)\n",
    "                start = (num_chunks - 1) * token_len\n",
    "                curr_chunk = curr_chunk[-cover_content:] + line[start:end]\n",
    "                chunk_text.append(curr_chunk)\n",
    "            elif curr_len + line_len <= token_len:\n",
    "                # 当前片段长度未超过限制时，继续累加\n",
    "                curr_chunk += line + '\\n'\n",
    "                curr_len += line_len + 1\n",
    "            else:\n",
    "                chunk_text.append(curr_chunk)  # 保存当前片段\n",
    "                curr_chunk = curr_chunk[-cover_content:] + line\n",
    "                curr_len = line_len + cover_content\n",
    "\n",
    "        if curr_chunk:\n",
    "            chunk_text.append(curr_chunk)\n",
    "\n",
    "        return chunk_text\n",
    "\n",
    "    @classmethod\n",
    "    def read_file_content(cls, file_path: str):\n",
    "        \"\"\"\n",
    "        读取文件内容，根据文件类型选择不同的读取方式。\n",
    "        :param file_path: 文件路径\n",
    "        :return: 文件内容\n",
    "        \"\"\"\n",
    "        if file_path.endswith('.pdf'):\n",
    "            return cls.read_pdf(file_path)\n",
    "        elif file_path.endswith('.md'):\n",
    "            return cls.read_markdown(file_path)\n",
    "        elif file_path.endswith('.txt'):\n",
    "            return cls.read_text(file_path)\n",
    "        else:\n",
    "            raise ValueError(\"Unsupported file type\")\n",
    "\n",
    "    @classmethod\n",
    "    def read_pdf(cls, file_path: str):\n",
    "        \"\"\"\n",
    "        读取 PDF 文件内容。\n",
    "        :param file_path: PDF 文件路径\n",
    "        :return: PDF 文件中的文本内容\n",
    "        \"\"\"\n",
    "        with open(file_path, 'rb') as file:\n",
    "            reader = PyPDF2.PdfReader(file)\n",
    "            text = \"\"\n",
    "            for page_num in range(len(reader.pages)):\n",
    "                text += reader.pages[page_num].extract_text()\n",
    "            return text\n",
    "\n",
    "    @classmethod\n",
    "    def read_markdown(cls, file_path: str):\n",
    "        \"\"\"\n",
    "        读取 Markdown 文件内容，并将其转换为纯文本。\n",
    "        :param file_path: Markdown 文件路径\n",
    "        :return: 纯文本内容\n",
    "        \"\"\"\n",
    "        with open(file_path, 'r', encoding='utf-8') as file:\n",
    "            md_text = file.read()\n",
    "            html_text = markdown.markdown(md_text)\n",
    "            # 使用 BeautifulSoup 从 HTML 中提取纯文本\n",
    "            soup = BeautifulSoup(html_text, 'html.parser')\n",
    "            plain_text = soup.get_text()\n",
    "            # 使用正则表达式移除网址链接\n",
    "            text = re.sub(r'http\\S+', '', plain_text) \n",
    "            return text\n",
    "\n",
    "    @classmethod\n",
    "    def read_text(cls, file_path: str):\n",
    "        \"\"\"\n",
    "        读取普通文本文件内容。\n",
    "        :param file_path: 文本文件路径\n",
    "        :return: 文件内容\n",
    "        \"\"\"\n",
    "        with open(file_path, 'r', encoding='utf-8') as file:\n",
    "            return file.read()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "d9d819e5-5a85-47ee-bdf2-52998587d5d5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:46:03.581411Z",
     "iopub.status.busy": "2025-07-23T05:46:03.580890Z",
     "iopub.status.idle": "2025-07-23T05:46:03.587665Z",
     "shell.execute_reply": "2025-07-23T05:46:03.587665Z",
     "shell.execute_reply.started": "2025-07-23T05:46:03.581411Z"
    }
   },
   "outputs": [],
   "source": [
    "class Documents:\n",
    "    \"\"\"\n",
    "    文档类，用于读取已分好类的 JSON 格式文档。\n",
    "    \"\"\"\n",
    "    def __init__(self, path: str = '') -> None:\n",
    "        self.path = path\n",
    "\n",
    "    def get_content(self):\n",
    "        \"\"\"\n",
    "        读取 JSON 格式的文档内容。\n",
    "        :return: JSON 文档的内容\n",
    "        \"\"\"\n",
    "        with open(self.path, mode='r', encoding='utf-8') as f:\n",
    "            content = json.load(f)\n",
    "        return content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "19f6bacc-8a72-401a-a9a9-ec15625e005e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:47:57.585710Z",
     "iopub.status.busy": "2025-07-23T05:47:57.584712Z",
     "iopub.status.idle": "2025-07-23T05:47:57.593804Z",
     "shell.execute_reply": "2025-07-23T05:47:57.592832Z",
     "shell.execute_reply.started": "2025-07-23T05:47:57.585710Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "支持的文件列表： ['./data\\\\凡人修仙传第一章.txt']\n"
     ]
    }
   ],
   "source": [
    "# 初始化 ReadFiles 类，指定文件目录路径\n",
    "file_reader = ReadFiles(path=\"./data\")\n",
    "\n",
    "# 获取目录下所有支持的文件类型\n",
    "file_list = file_reader.get_files()\n",
    "print(\"支持的文件列表：\", file_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "bed9d8a4-47f8-4db9-82ef-6b2585c1810e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:47:59.720732Z",
     "iopub.status.busy": "2025-07-23T05:47:59.720170Z",
     "iopub.status.idle": "2025-07-23T05:47:59.730776Z",
     "shell.execute_reply": "2025-07-23T05:47:59.729656Z",
     "shell.execute_reply.started": "2025-07-23T05:47:59.720732Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "分块后的文档分段长度： 12\n"
     ]
    }
   ],
   "source": [
    "# 将文件内容读取并分块\n",
    "document_chunks = file_reader.get_content(max_token_len=600, cover_content=150)\n",
    "# print(\"分块后的文档内容：\", document_chunks)\n",
    "print(\"分块后的文档分段长度：\", len(document_chunks))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "25608652-a91c-4fdc-a69e-f7a13b71b1c7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:48:01.953794Z",
     "iopub.status.busy": "2025-07-23T05:48:01.953794Z",
     "iopub.status.idle": "2025-07-23T05:48:01.960520Z",
     "shell.execute_reply": "2025-07-23T05:48:01.959970Z",
     "shell.execute_reply.started": "2025-07-23T05:48:01.953794Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "第一章山边小村\n",
      "\n",
      "二愣子睁大着双眼，直直望着茅草和烂泥糊成的黑屋顶，身上盖着的旧棉被，已呈深黄色，看不出原来的本来面目，还若有若无的散着淡淡的霉味。\n",
      "\n",
      "在他身边紧挨着的另一人，是二哥韩铸，酣睡的十分香甜，从他身上不时传来轻重不一的阵阵打呼声。\n",
      "\n",
      "离床大约半丈远的地方，是一堵黄泥糊成的土墙，因为时间过久，墙壁上裂开了几丝不起眼的细长口子，从这些裂纹中，隐隐约约的传来韩母唠唠叨叨的埋怨声，偶尔还掺杂着韩父，抽旱烟杆的“啪嗒”“啪嗒”吸允声。\n",
      "\n",
      "二愣子缓缓的闭上已有些涩的双目，迫使自己尽早进入深深的睡梦中。他心里非常清楚，再不老实入睡的话，明天就无法早起些了，也就无法和其他约好的同伴一起进山拣干柴。\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(document_chunks[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3c1a725-4c68-49ec-8e7a-828841d4ee97",
   "metadata": {},
   "source": [
    "# 词向量数据库与向量检索模块"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa24ab22-0a93-43c7-96cd-ed151294e369",
   "metadata": {},
   "source": [
    "- 量数据库用于存储文档片段及其对应的向量表示，而检索模块则根据用户提出的问题（Query）在数据库中检索相关的文档片段。通过这些功能，我们创建的简易 RAG 能够根据输入的查询快速找到最相关的文档片段。\n",
    "---\n",
    "为了构建这个向量数据库，我们需要以下几个关键功能：\n",
    "1. 持久化存储（persist）： 将数据库存储到本地，便于下次加载使用。\n",
    "2. 加载数据库（load_vector）： 从本地文件加载已经存储的向量和文档。\n",
    "3. 获取向量表示（get_vector）： 将文档转化为向量表示并存储。\n",
    "4. 检索（query）： 根据用户的 Query，检索数据库中的相关文档片段。\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49a5db4c-8e76-4855-98bd-0397c6299540",
   "metadata": {},
   "source": [
    "- 创建一个基础的 VectorStore 类，提供上述功能的框架。通过这个类，我们能够将文档片段转化为向量存储，加载本地数据库，进行检索。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "f1b12bb5-2fad-41c6-a43a-71f54cf4744e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:51:02.976805Z",
     "iopub.status.busy": "2025-07-23T05:51:02.975783Z",
     "iopub.status.idle": "2025-07-23T05:51:02.991229Z",
     "shell.execute_reply": "2025-07-23T05:51:02.990207Z",
     "shell.execute_reply.started": "2025-07-23T05:51:02.976805Z"
    }
   },
   "outputs": [],
   "source": [
    "class VectorStore:\n",
    "    def __init__(self, document: List[str] = None) -> None:\n",
    "        \"\"\"\n",
    "        初始化向量存储类，存储文档和对应的向量表示。\n",
    "        :param document: 文档列表，默认为空。\n",
    "        \"\"\"\n",
    "        if document is None:\n",
    "            document = []\n",
    "        self.document = document  # 存储文档内容\n",
    "        self.vectors = []  # 存储文档的向量表示\n",
    "    \n",
    "    def get_vector_content(self, EmbeddingModel: BaseEmbeddings) -> List[List[float]]:\n",
    "        \"\"\"\n",
    "        使用传入的 Embedding 模型将文档向量化。\n",
    "        :param EmbeddingModel: 传入的用于生成向量的模型（需继承 BaseEmbeddings 类）。\n",
    "        :return: 返回文档对应的向量列表。\n",
    "        \"\"\"\n",
    "        # 遍历所有文档，获取每个文档的向量表示\n",
    "        self.vectors = [EmbeddingModel.get_embedding(doc) for doc in self.document]\n",
    "        return self.vectors\n",
    "    \n",
    "    def persist(self, path: str = 'storage'):\n",
    "        \"\"\"\n",
    "        将文档和对应的向量表示持久化到本地目录中，以便后续加载使用。\n",
    "        :param path: 存储路径，默认为 'storage'。\n",
    "        \"\"\"\n",
    "        if not os.path.exists(path):\n",
    "            os.makedirs(path)  # 如果路径不存在，创建路径\n",
    "        # 保存向量为 numpy 文件\n",
    "        np.save(os.path.join(path, 'vectors.npy'), self.vectors)\n",
    "        # 将文档内容存储到文本文件中\n",
    "        with open(os.path.join(path, 'documents.txt'), 'w') as f:\n",
    "            for doc in self.document:\n",
    "                f.write(f\"{doc}\\n\")\n",
    "    \n",
    "    def load_vector(self, path: str = 'storage'):\n",
    "        \"\"\"\n",
    "        从本地加载之前保存的文档和向量数据。\n",
    "        :param path: 存储路径，默认为 'storage'。\n",
    "        \"\"\"\n",
    "        # 加载保存的向量数据\n",
    "        self.vectors = np.load(os.path.join(path, 'vectors.npy')).tolist()\n",
    "        # 加载文档内容\n",
    "        with open(os.path.join(path, 'documents.txt'), 'r') as f:\n",
    "            self.document = [line.strip() for line in f.readlines()]\n",
    "\n",
    "    def get_similarity(self, vector1: List[float], vector2: List[float]) -> float:\n",
    "        \"\"\"\n",
    "        计算两个向量的余弦相似度。\n",
    "        :param vector1: 第一个向量。\n",
    "        :param vector2: 第二个向量。\n",
    "        :return: 返回两个向量的余弦相似度，范围从 -1 到 1。\n",
    "        \"\"\"\n",
    "        dot_product = np.dot(vector1, vector2)\n",
    "        magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)\n",
    "        if not magnitude:\n",
    "            return 0\n",
    "        return dot_product / magnitude\n",
    "\n",
    "    def query(self, query: str, EmbeddingModel: BaseEmbeddings, k: int = 1) -> List[str]:\n",
    "        \"\"\"\n",
    "        根据用户的查询文本，检索最相关的文档片段。\n",
    "        :param query: 用户的查询文本。\n",
    "        :param EmbeddingModel: 用于将查询向量化的嵌入模型。\n",
    "        :param k: 返回最相似的文档数量，默认为 1。\n",
    "        :return: 返回最相似的文档列表。\n",
    "        \"\"\"\n",
    "        # 将查询文本向量化\n",
    "        query_vector = EmbeddingModel.get_embedding(query)\n",
    "        # 计算查询向量与每个文档向量的相似度\n",
    "        similarities = [self.get_similarity(query_vector, vector) for vector in self.vectors]\n",
    "        # 获取相似度最高的 k 个文档索引\n",
    "        top_k_indices = np.argsort(similarities)[-k:][::-1]\n",
    "        # 返回对应的文档内容\n",
    "        return [self.document[idx] for idx in top_k_indices]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e60fe88b-9921-45fb-9515-a328d59577b7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T13:11:02.007254Z",
     "iopub.status.busy": "2025-07-23T13:11:02.006234Z",
     "iopub.status.idle": "2025-07-23T13:11:02.351058Z",
     "shell.execute_reply": "2025-07-23T13:11:02.350043Z",
     "shell.execute_reply.started": "2025-07-23T13:11:02.007254Z"
    }
   },
   "source": [
    "```python\n",
    "import numpy as np\n",
    "similarities = [0.3, 0.1, 0.5, 0.2]\n",
    "\n",
    "# np.argsort(similarities) 返回 [1, 3, 0, 2]（因为 0.1 最小，索引 1 排第一；0.2 次小，索引 3 排第二，依此类推）。\n",
    "np.argsort(similarities) # array([1, 3, 0, 2], dtype=int64)\n",
    "# [-k:]：切片操作，取排序后索引数组的最后 k 个元素（即最大的 k 个值的索引）\n",
    "# [::-1]：将数组逆序，从而将索引从大到小排列。\n",
    "top_k_indices = np.argsort(similarities)[-k:][::-1]\n",
    "```\n",
    "假设：similarities = [0.3, 0.1, 0.5, 0.2]，\n",
    "k = 2。\n",
    "步骤：\n",
    "np.argsort(similarities) → [1, 3, 0, 2]（按值从小到大排序的索引）。\n",
    "[-k:] → [0, 2]（取最后 k 个索引，即最大的 k 个值的索引）。\n",
    "[::-1] → [2, 0]（逆序，从大到小排列）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "6abe7c0c-7b1b-48bd-bda4-bd6c285bf27e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:51:03.439249Z",
     "iopub.status.busy": "2025-07-23T05:51:03.439249Z",
     "iopub.status.idle": "2025-07-23T05:51:09.679337Z",
     "shell.execute_reply": "2025-07-23T05:51:09.678786Z",
     "shell.execute_reply.started": "2025-07-23T05:51:03.439249Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "检索结果： ['深度学习是一种特殊的机器学习方法。']\n"
     ]
    }
   ],
   "source": [
    "# 初始化文档列表\n",
    "documents = [\n",
    "    \"机器学习是人工智能的一个分支。\",\n",
    "    \"深度学习是一种特殊的机器学习方法。\",\n",
    "    \"监督学习是一种训练模型的方式。\",\n",
    "    \"强化学习通过奖励和惩罚进行学习。\",\n",
    "    \"无监督学习不依赖标签数据。\",\n",
    "]\n",
    "\n",
    "# 创建向量数据库\n",
    "vector_store = VectorStore(document=documents)\n",
    "\n",
    "# 使用 OpenAI Embedding 模型对文档进行向量化\n",
    "embedding_model = OpenAIEmbedding()\n",
    "\n",
    "# 获取文档向量并存储\n",
    "vector_store.get_vector(embedding_model)\n",
    "\n",
    "# 持久化存储到本地\n",
    "vector_store.persist('storage')\n",
    "\n",
    "# 模拟用户查询\n",
    "query = \"什么是深度学习？\"\n",
    "result = vector_store.query(query, embedding_model)\n",
    "\n",
    "print(\"检索结果：\", result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "35077394-7fbb-4e1c-bc4a-d5fa80e99034",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:51:20.061420Z",
     "iopub.status.busy": "2025-07-23T05:51:20.061420Z",
     "iopub.status.idle": "2025-07-23T05:51:20.069610Z",
     "shell.execute_reply": "2025-07-23T05:51:20.069066Z",
     "shell.execute_reply.started": "2025-07-23T05:51:20.061420Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0.01502207  0.02491738 -0.00439076 ... -0.01719366 -0.0138915\n",
      "   0.01127215]\n",
      " [-0.01284684  0.02200937 -0.00358062 ... -0.01165089 -0.00908055\n",
      "  -0.01718699]\n",
      " [-0.02281043  0.01748055 -0.0156418  ... -0.00208568  0.00614055\n",
      "   0.01556725]\n",
      " [-0.0254771   0.01967829 -0.00521657 ... -0.02507718 -0.00610462\n",
      "  -0.00051681]\n",
      " [ 0.02986923  0.00549699 -0.00834242 ... -0.00765595 -0.01394945\n",
      "   0.01053807]]\n"
     ]
    }
   ],
   "source": [
    "loaded_array = np.load('./storage/vectors.npy')\n",
    "\n",
    "print(loaded_array)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ffa3076e-9275-4eeb-bc57-8327188cdb06",
   "metadata": {},
   "source": [
    "# 大模型问答模块\n",
    "编写一个基类 BaseModel，它包含两个主要方法：\n",
    "- chat：负责处理用户的输入并生成回答。\n",
    "- load_model：如果是使用本地模型，这个方法负责加载模型。如果使用 API 模型（如 OpenAI），可以不用实现这个方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "2ec7be05-6f4d-44ca-9583-e55960e2af12",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:56:08.382921Z",
     "iopub.status.busy": "2025-07-23T05:56:08.381904Z",
     "iopub.status.idle": "2025-07-23T05:56:08.390594Z",
     "shell.execute_reply": "2025-07-23T05:56:08.389561Z",
     "shell.execute_reply.started": "2025-07-23T05:56:08.382921Z"
    }
   },
   "outputs": [],
   "source": [
    "class BaseModel:\n",
    "    \"\"\"\n",
    "    基础模型类，作为所有模型的基类。\n",
    "    包含一些通用的接口，如加载模型、生成回答等。\n",
    "    \"\"\"\n",
    "    def __init__(self, path: str = '') -> None:\n",
    "        self.path = path  # 用于存储模型文件的路径，默认为空。\n",
    "\n",
    "    def chat(self, prompt: str, history: List[dict], content: str) -> str:\n",
    "        \"\"\"\n",
    "        使用模型生成回答的抽象方法。\n",
    "        :param prompt: 用户的提问内容\n",
    "        :param history: 之前的对话历史（字典列表）\n",
    "        :param content: 提供的上下文内容\n",
    "        :return: 模型生成的答案\n",
    "        \"\"\"\n",
    "        pass  # 具体的实现由子类提供\n",
    "\n",
    "    def load_model(self):\n",
    "        \"\"\"\n",
    "        加载模型的方法，通常用于本地模型。\n",
    "        \"\"\"\n",
    "        pass  # 如果是 API 模型，可能不需要实现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "0c62db95-3635-4aec-80e4-95504c6e3906",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:56:09.415612Z",
     "iopub.status.busy": "2025-07-23T05:56:09.414604Z",
     "iopub.status.idle": "2025-07-23T05:56:09.423317Z",
     "shell.execute_reply": "2025-07-23T05:56:09.423317Z",
     "shell.execute_reply.started": "2025-07-23T05:56:09.415612Z"
    }
   },
   "outputs": [],
   "source": [
    "# 借助GPT4o模型进行对话\n",
    "class GPT4oChat(BaseModel):\n",
    "    \"\"\"\n",
    "    基于 GPT-4o 模型的对话类，继承自 BaseModel。\n",
    "    主要用于通过 OpenAI API 来生成对话回答。\n",
    "    \"\"\"\n",
    "    def __init__(self, api_key: str, base_url: str = \"https://api.openai-hk.com/v1\") -> None:\n",
    "        \"\"\"\n",
    "        初始化 GPT-4o 模型。\n",
    "        :param api_key: OpenAI API 的密钥\n",
    "        :param base_url: 用于访问 OpenAI API 的基础 URL，默认为代理 URL\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.client = OpenAI(api_key=api_key, base_url=base_url)  # 初始化 OpenAI 客户端\n",
    "\n",
    "    def chat(self, prompt: str, history: List = [], content: str = '') -> str:\n",
    "        \"\"\"\n",
    "        使用 GPT-4o 生成回答。\n",
    "        :param prompt: 用户的提问\n",
    "        :param history: 之前的对话历史（可选）\n",
    "        :param content: 可参考的上下文信息（可选）\n",
    "        :return: 生成的回答\n",
    "        \"\"\"\n",
    "        # 构建包含问题和上下文的完整提示\n",
    "        full_prompt = PROMPT_TEMPLATE['GPT4o_PROMPT_TEMPLATE'].format(question=prompt, context=content)\n",
    "\n",
    "        # 调用 GPT-4o 模型进行推理\n",
    "        response = self.client.chat.completions.create(\n",
    "            model=\"gpt-4o-mini\",  # 使用 GPT-4o 小型模型\n",
    "            messages=[\n",
    "                {\"role\": \"user\", \"content\": full_prompt}\n",
    "            ]\n",
    "        )\n",
    "\n",
    "        # 返回模型生成的第一个回答\n",
    "        return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "fc926691-2c56-42db-91f6-1892ab6af2af",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:56:10.914770Z",
     "iopub.status.busy": "2025-07-23T05:56:10.914770Z",
     "iopub.status.idle": "2025-07-23T05:56:10.921189Z",
     "shell.execute_reply": "2025-07-23T05:56:10.920642Z",
     "shell.execute_reply.started": "2025-07-23T05:56:10.914770Z"
    }
   },
   "outputs": [],
   "source": [
    "# 提示模板\n",
    "PROMPT_TEMPLATE = dict(\n",
    "    GPT4o_PROMPT_TEMPLATE=\"\"\"\n",
    "    下面有一个或许与这个问题相关的参考段落，若你觉得参考段落能和问题相关，则先总结参考段落的内容。\n",
    "    若你觉得参考段落和问题无关，则使用你自己的原始知识来回答用户的问题，并且总是使用中文来进行回答。\n",
    "    问题: {question}\n",
    "    可参考的上下文：\n",
    "    ···\n",
    "    {context}\n",
    "    ···\n",
    "    有用的回答:\"\"\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "385c5f97-634a-46da-aa95-cda29c2ec918",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:59:33.820319Z",
     "iopub.status.busy": "2025-07-23T05:59:33.819322Z",
     "iopub.status.idle": "2025-07-23T05:59:48.576825Z",
     "shell.execute_reply": "2025-07-23T05:59:48.575815Z",
     "shell.execute_reply.started": "2025-07-23T05:59:33.820319Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "“二愣子”好听了哪里去。\n",
      "\n",
      "因此，韩立虽然并不喜欢这个称呼，但也只能这样一直的自我安慰着。\n",
      "\n",
      "韩立外表长得很不起眼，皮肤黑黑的，就是一个普通的农家小孩模样。但他的内心深处，却比同龄人早熟了许多，他从小就向往外面世界的富饶繁华，梦想有一天，他能走出这个巴掌大的村子，去看看老张叔经常所说的外面世界。\n",
      "\n",
      "当韩立的这个想法，一直没敢和其他人说起过。否则，一定会使村里人感到愕然，一个乳臭未干的小屁孩，竟然会有这么一个大人也不敢轻易想的念头。要知道，其他同韩立差不多大的小孩，都还只会满村的追鸡摸狗，更别说会有离开故土，这么一个古怪的念头。\n",
      "韩立一家七口人，有两个兄长，一个姐姐，还有一个小妹，他在家里排行老四，今年刚十岁，家里的生活很清苦，一年也吃不上几顿带荤腥的饭菜，全家人一直在温饱线上徘徊着。\n",
      "\n",
      "\n",
      "参考段落提到，韩立一家有七口人，韩立有两个兄长，一个姐姐和一个小妹，他在家里排行老四。因此可以确定，韩立有兄弟姐妹。\n"
     ]
    }
   ],
   "source": [
    "# 加载并切分文档\n",
    "docs = ReadFiles('./data').get_content(max_token_len=600, cover_content=150)\n",
    "vector = VectorStore(docs)\n",
    "\n",
    "# 使用 OpenAI Embedding 模型进行向量化\n",
    "embedding = OpenAIEmbedding()\n",
    "vector.get_vector(EmbeddingModel=embedding)\n",
    "\n",
    "# 用户提出问题\n",
    "question = '韩立有兄弟姐妹吗？'\n",
    "\n",
    "# 在数据库中检索最相关的文档片段\n",
    "content = vector.query(question, EmbeddingModel=embedding, k=1)[0]\n",
    "print(content)\n",
    "\n",
    "# 使用 GPT4oChat 模型生成答案\n",
    "chat = GPT4oChat(api_key = os.getenv(\"OPENAI_API_KEY\"))  # 传入 OpenAI API 密钥\n",
    "print(chat.chat(question, [], content))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "511203f6-6e64-42db-b810-a05cf7316ece",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-07-23T05:59:07.169955Z",
     "iopub.status.busy": "2025-07-23T05:59:07.169955Z",
     "iopub.status.idle": "2025-07-23T05:59:19.094052Z",
     "shell.execute_reply": "2025-07-23T05:59:19.094052Z",
     "shell.execute_reply.started": "2025-07-23T05:59:07.169955Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "参考段落提到韩立一家有七口人，包括两个兄长、一个姐姐和一个小妹，韩立在家中排行老四。因此，韩立确实有兄弟姐妹。"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 完整函数封装\n",
    "def run_mini_rag(question: str, knowledge_base_path: str, k: int = 1) -> str:\n",
    "    \"\"\"\n",
    "    运行一个简化版的RAG项目。\n",
    "    \n",
    "    :param question: 用户提出的问题\n",
    "    :param knowledge_base_path: 知识库的路径，包含文档的文件夹路径\n",
    "    :param api_key: OpenAI API密钥，用于调用GPT-4o模型\n",
    "    :param k: 返回与问题最相关的k个文档片段，默认为1\n",
    "    :return: 返回GPT-4o模型生成的回答\n",
    "    \"\"\"\n",
    "    api_key = os.getenv(\"OPENAI_API_KEY\")\n",
    "    # 1. 加载并切分文档\n",
    "    docs = ReadFiles(knowledge_base_path).get_content(max_token_len=600, cover_content=150)\n",
    "    vector = VectorStore(docs)\n",
    "\n",
    "    # 2. 使用 OpenAI Embedding 模型进行向量化\n",
    "    embedding = OpenAIEmbedding()\n",
    "    vector.get_vector(EmbeddingModel=embedding)\n",
    "\n",
    "    # 3. 将向量和文档保存到本地（可选）\n",
    "    vector.persist(path='storage')\n",
    "\n",
    "    # 4. 在数据库中检索最相关的文档片段\n",
    "    content = vector.query(question, EmbeddingModel=embedding, k=k)[0]\n",
    "\n",
    "    # 5. 使用 GPT-4o 生成答案\n",
    "    chat = GPT4oChat(api_key=api_key)\n",
    "    answer = chat.chat(question, [], content)\n",
    "    \n",
    "    return answer\n",
    "\n",
    "# 测试运行\n",
    "question = '韩立有兄弟姐妹吗？'\n",
    "knowledge_base_path = './data'\n",
    "answer = run_mini_rag(question, knowledge_base_path)\n",
    "display(Markdown(answer))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "graphrag",
   "language": "python",
   "name": "graphrag"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
